package main

import (
	_ "bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/gorilla/websocket"
	"github.com/julienschmidt/httprouter"
	"github.com/mikepb/go-serial"
	tserial "github.com/tarm/serial"
)

type PortInfo struct {
	Name string
	Desc string
	Info *serial.Info `json:"-"`
}

var devices []PortInfo

var (
	crtFile  = "https://www.codepku.com/misc/local.codepku/local.crt"
	keyFile  = "https://www.codepku.com/misc/local.codepku/local.key"
	infoFile = "https://www.codepku.com/misc/local.codepku/pico.txt"
	PORT     = "26337" // 端口（手机 codepku前几位）
)

func getSerialPortsInfo() ([]PortInfo, error) {
	ports, err := serial.ListPorts()
	if err != nil {
		return nil, err
	}

	portList := make([]PortInfo, 0, 5)
	for _, p := range ports {
		pt := PortInfo{p.Name(), p.Name() + "&" + p.Description() + "&" + p.USBManufacturer() + "&" + p.USBProduct(), p}
	    fmt.Println(pt.Desc)
		if strings.Contains(strings.ToLower(pt.Desc), "arduino") ||
			strings.Contains(strings.ToLower(pt.Desc), "ch340") ||
			strings.Contains(strings.ToLower(pt.Desc), "usbserial") ||
			strings.Contains(strings.ToLower(pt.Desc), "wchusbserial") { // ch340芯片在Mac下显示wchusbserial1420
		    fmt.Println("Connect to " + pt.Desc)
			portList = append(portList, pt)
		}
	}

	return portList, nil
}
func getDeviceById(id string) (*PortInfo, error) {
	for _, dev := range devices {
		if dev.Name == id {
			return &dev, nil
		}
	}
	return nil, fmt.Errorf("No device found at " + id)
}

/*
func serialWrite(p *tserial.Port, i int) {
	for ; i > 0; i-- {
		_, err := p.Write([]byte{1})
		if err != nil {
			log.Fatal(err)
		}
		time.Sleep(time.Millisecond * 50)
	}
}
*/

type LogRouter struct {
	LogLevel  string
	LogMethod string
	Router    *httprouter.Router
}

func addHeaders(w http.ResponseWriter) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
}

func (router LogRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	if !strings.EqualFold(router.LogLevel, "none") {
		log.Println(r.URL.Path)
	}
	addHeaders(w)
	router.Router.ServeHTTP(w, r)
}

var wsUpgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func pingHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprintf(w, "OK")
}

func optionResponse(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	w.Header().Add("Access-Control-Allow-Headers", "X-Requested-With, Content-type, X-CSRF-Token")
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("WSS connected!")
	conn, err := wsUpgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	//fmt.Println(conn)
	// serve websocket
	//fmt.Println("ws connected")
	var selectedDevice = currentOpenedDevice
	var usedBuf = currentDeviceBuf
	// var packetPrefix = currentDevicePacketPrefix
	defer selectedDevice.Close()
	defer conn.Close()
	for {
		messageType, p, err := conn.ReadMessage()

		if err != nil {
			log.Println(err)
			return
		}

		if _, err := selectedDevice.Write(p); err != nil {
			log.Println(err)
			return
		}

		//echo

		//fake pico data
		// fake := []byte{248, 4, 135, 127, 143, 127, 151, 127, 159, 127, 167, 126, 175, 114, 176, 40, 188, 0}
		//这里要计算需要等待的时间，根据帧长度和波特率计算（也不准），经测试，picoboard可以先用10ms
		time.Sleep(time.Millisecond * 50)

		size, err := selectedDevice.Read(usedBuf)
		if err != nil {
			log.Println(err)
			return
		}
		//log.Printf("%d bytes sent\n", size)
		//log.Println(usedBuf[:size])

		if err = conn.WriteMessage(messageType, usedBuf[:size]); err != nil {
			log.Println(err)
			return
		}
		/*
			_, err = selectedDevice.Read(usedBuf)
			if err != nil {
				log.Println(err)
				return
			}
			var fake []byte
			if bytes.HasPrefix(usedBuf, packetPrefix) {
				fake = usedBuf
			} else {
				fmt.Printf("报文不匹配，丢弃")
				fmt.Println(usedBuf)
				continue
				// 起始报头不对，丢弃一个字节，继续读取。
			}

			if err = conn.WriteMessage(messageType, fake); err != nil {
				log.Println(err)
				return
			}
		*/
	}
}

func deviceList(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	// 列出所有设备及信息
	devices, _ = getSerialPortsInfo()
	if len(devices) < 1 {
		log.Println("Waiting for connecting...")
	} else {
		resp, _ := json.Marshal(devices)
		fmt.Fprintf(w, string(resp))
	}
}

var currentOpenedDevice *tserial.Port
var currentDeviceBuf []byte //根据设备需求设置一次读取并发送的数据包大小。
var currentDevicePacketPrefix []byte

func sliceAtoByte(sa []string) ([]byte, error) {
	si := make([]byte, 0, len(sa))
	for _, a := range sa {
		i, err := strconv.Atoi(a)
		if err != nil {
			return si, err
		}
		si = append(si, byte(i))
	}
	return si, nil
}

func openDevice(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	id := r.FormValue("deviceId")
	_, err := getDeviceById(id)
	if err != nil {
		fmt.Fprintf(w, err.Error())
	} else {
		if currentOpenedDevice != nil {
			log.Println("Closing Device: " + id)
			currentOpenedDevice.Close()
		}
		log.Println("Now open device " + id)
		r.ParseForm()
		//log.Println(r.Form)
		bitRate, _ := strconv.Atoi(r.FormValue("bitRate"))
		stopBits, _ := strconv.Atoi(r.FormValue("stopBits"))
		c := &tserial.Config{Name: id, Baud: bitRate, ReadTimeout: time.Second * 2, StopBits: tserial.StopBits(stopBits)}

		//		if sb, err := strconv.Atoi(r.FormValue("stopBits")); err == nil {
		//			options.StopBits = sb
		//		}
		//		if fc, err := strconv.Atoi(r.FormValue("ctsFlowControl")); err == nil {
		//			options.FlowControl = fc
		//		}

		bufSize, err := strconv.Atoi(r.FormValue("packetSize"))
		if err != nil {
			bufSize = 8
		}
		// used to check if the data read into a slice is a full packet.
		packetPrefix := r.Form["packetPrefix[]"]
		if len(packetPrefix) > 0 {
			tmp, _ := sliceAtoByte(packetPrefix)
			currentDevicePacketPrefix = []byte(tmp)
		}

		if openedDevice, err := tserial.OpenPort(c); err == nil {
			currentOpenedDevice = openedDevice
			currentDeviceBuf = make([]byte, bufSize)
			log.Println("Device Opened Successfully")
			fmt.Fprintf(w, "OK")
		} else {
			log.Println(err)
			fmt.Fprintf(w, "Failed")
		}
	}
}
func closeDevice(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

}

func downloadToTempFile(uri string) string {
	tr := &http.Transport{
		TLSClientConfig:    &tls.Config{InsecureSkipVerify: true},
		DisableCompression: true,
	}
	client := &http.Client{Transport: tr}
	client.Jar, _ = cookiejar.New(nil)

	resp, err := client.Get(uri) // Get(uri + time.Now().Format("200601")) 添加月份，方便CDN更新。但是直接从源站获取，可以方便通过日志获取打开次数
	//	body, err := ioutil.ReadAll(resp.Body)
	//	fmt.Println(string(body), err)
	if err != nil {
		log.Fatal("Failed to download SSL cert")
	}

	tmpdir, err := ioutil.TempDir("", "codepku-cert")
	if err != nil {
		log.Fatal("Failed to create temp folder: ", err)
	}
	// defer os.RemoveAll(tmpdir)这里不能删除，后边还要编译上传代码
	filename := tmpdir + string(os.PathSeparator) + path.Base(uri)
	fout, err := os.Create(filename)
	log.Println("Create folder and file: " + fout.Name())
	_, err = io.Copy(fout, resp.Body)
	if err != nil {
		log.Fatal("Failed to write SSL cert to file")
	}
	defer fout.Close()

	return filename
}
func main() {

	certfile := downloadToTempFile(crtFile)
	keyfile := downloadToTempFile(keyFile)
	infofile := downloadToTempFile(infoFile)

	info, _ := ioutil.ReadFile(infofile)
	log.Printf(string(info))

	router := httprouter.New()

	router.HandlerFunc("GET", "/ws", wsHandler)
	router.GET("/", pingHandler)
	// 下面这些方式用http同步获取到结果
	router.POST("/device-list", deviceList)
	router.POST("/open-device", openDevice)
	router.POST("/close-device", closeDevice)

	router.OPTIONS("/*path", optionResponse)

	//logrouter := LogRouter{"debug", "default", router}
	logrouter := LogRouter{"none", "default", router}
	log.Println("编玩边学硬件助手")
	err := http.ListenAndServeTLS("127.0.0.1:"+PORT,
		certfile,
		keyfile,
		logrouter)
	if err != nil {
		log.Println("启动失败，请检查"+PORT+"端口是否已被占用，请退出占用该端口的程序或者重启电脑后重新尝试: ", err)
		time.Sleep(10 * time.Second)
	}
	/*
		c := &tserial.Config{Name: "COM5", Baud: 38400, StopBits: 0}
		port, err := tserial.OpenPort(c)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(port)
		go serialWrite(port, 20)

		buf := make([]byte, 0)
		n := 0
		for n < 18 {
			buf2 := make([]byte, 1)
			n2, err := port.Read(buf2)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Println(n2)
			buf = append(buf, buf2[:n2]...)
			n += n2
		}

		log.Println(buf)
	*/
}
